Conversation
Walkthrough구글 웹 클라이언트 ID를 빌드 설정에 추가하고, AndroidX CredentialManager 기반의 GoogleLoginHelper와 OAuth UI/뷰모델 확장을 통해 기존 카카오 로그인에 구글 로그인을 통합했습니다. ProGuard 규칙과 라이브러리 의존성도 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant OauthScreen
participant OauthViewModel
participant GoogleLoginHelper
participant CredentialManager
participant Backend
User->>OauthScreen: 구글 로그인 클릭
OauthScreen->>OauthViewModel: GoogleLoginClick 발송
OauthViewModel->>OauthScreen: StartGoogleLogin 사이드이펙트 (또는 이벤트)
OauthScreen->>GoogleLoginHelper: requestGoogleLogin(onSuccess,onFailure)
GoogleLoginHelper->>CredentialManager: GetCredentialRequest (webClientId 포함)
CredentialManager->>User: 구글 계정 선택 UI 표시
User->>CredentialManager: 계정 선택
CredentialManager->>GoogleLoginHelper: GoogleIdTokenCredential 반환
GoogleLoginHelper->>OauthScreen: onSuccess(idToken)
OauthScreen->>OauthViewModel: GoogleLoginSuccess(provider="GOOGLE", accessToken=idToken)
OauthViewModel->>Backend: postSocialLogin(provider, accessToken)
Backend->>OauthViewModel: JWT 응답
OauthViewModel->>OauthViewModel: SaveJwtToken 처리
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 분 Possibly related PRs
Suggested labels
Suggested reviewers
전체 설명구글 로그인 기능을 추가합니다. 구글 웹 클라이언트 ID를 빌드 설정에 추가하고, AndroidX CredentialManager를 활용한 GoogleLoginHelper 클래스를 구현하여 구글 인증 토큰을 획득합니다. OAuth 화면과 뷰모델을 확장해 기존 카카오 로그인과 함께 구글 로그인 흐름을 통합합니다. 변경사항
시퀀스 다이어그램sequenceDiagram
actor User
participant OauthScreen
participant OauthViewModel
participant GoogleLoginHelper
participant CredentialManager
participant Backend
User->>OauthScreen: 구글 로그인 클릭
OauthScreen->>OauthViewModel: GoogleLoginClick 발송
OauthViewModel->>OauthViewModel: StartGoogleLogin 사이드이펙트 생성
OauthScreen->>GoogleLoginHelper: requestGoogleLogin 호출
GoogleLoginHelper->>CredentialManager: 자격증명 요청
CredentialManager->>User: 구글 계정 선택 UI
User->>CredentialManager: 계정 선택
CredentialManager->>GoogleLoginHelper: GoogleIdTokenCredential 반환
GoogleLoginHelper->>OauthScreen: onSuccess (ID 토큰)
OauthScreen->>OauthViewModel: GoogleLoginSuccess 발송
OauthViewModel->>Backend: postSocialLogin (provider="GOOGLE", accessToken)
Backend->>OauthViewModel: JWT 토큰 반환
OauthViewModel->>OauthViewModel: SaveJwtToken 발송
코드 리뷰 예상 노력🎯 3 (Moderate) | ⏱️ ~20 분
관련 PR 가능성
제안 라벨
제안 검토자
시
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (2)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (2)
140-140: 동일한 이슈: 고정 높이 Spacer 검증 필요Line 129와 동일하게, 이 Spacer도 weight 기반에서 고정 높이(32.dp)로 변경되었습니다.
168-168: 동일한 이슈: 고정 높이 Spacer 검증 필요Line 129, 140과 동일하게, 이 Spacer도 weight 기반에서 고정 높이(24.dp)로 변경되었습니다.
🧹 Nitpick comments (4)
feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt (1)
56-76: 디버그 로그 메시지를 개선하세요.Line 73의 로그 태그
"asdasdasd"는 의미 없는 문자열입니다. 디버깅 시 로그를 식별하기 어렵습니다.다음과 같이 개선하세요:
}.onFailure { - Log.d("asdasdasd", it.toString()) + Log.e("OauthViewModel", "Social login failed: ${it.message}", it) }feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt (1)
50-50: GoogleLoginHelper의 생명주기 관리를 고려해보세요.
GoogleLoginHelper가remember로 생성되지만 명시적인 정리 로직이 없습니다. 현재는 큰 문제가 없어 보이지만, 향후 리소스 관리가 필요할 경우를 대비하여DisposableEffect를 사용하는 것을 고려해보세요.예시:
val googleLoginHelper = remember { GoogleLoginHelper(context = context) } DisposableEffect(Unit) { onDispose { // Clean up if needed in the future } }feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt (2)
25-25: 주석 처리된 코드를 정리하세요.여러 곳에 주석 처리된 코드(SERVER_URL, nonce, 로그 등)가 남아있습니다. 필요한 코드는 활성화하고, 불필요한 코드는 제거하여 코드의 가독성을 높이는 것을 권장합니다.
예시:
- Line 25:
SERVER_URL이 필요하다면 활성화하고, 아니면 제거- Lines 69-71: 디버그 로그가 필요하다면 BuildConfig.DEBUG로 조건부 활성화
- Lines 74, 77, 83: 에러 로그는 활성화 권장
Also applies to: 33-33, 69-71, 74-74, 77-77, 83-83
24-24: BuildConfig를 직접 참조하는 대신 주입받는 것을 고려해보세요.현재
WEB_CLIENT_ID를 companion object에서 BuildConfig를 직접 참조하고 있습니다. 테스트 용이성과 의존성 명시를 위해 생성자를 통해 주입받는 것을 고려해보세요.예시:
class GoogleLoginHelper( private val context: Context, private val webClientId: String = com.teamsolply.solply.buildconfig.BuildConfig.GOOGLE_WEB_CLIENT_ID ) { // ... private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() .setServerClientId(webClientId) // ... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
app/proguard-rules.pro(1 hunks)build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt(1 hunks)core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt(2 hunks)core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt(1 hunks)feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt(3 hunks)feature/oauth/build.gradle.kts(2 hunks)feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt(1 hunks)feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt(2 hunks)feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt(4 hunks)feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt(1 hunks)feature/oauth/src/main/res/values/strings.xml(1 hunks)gradle/libs.versions.toml(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt (4)
core/ui/src/main/java/com/teamsolply/solply/ui/base/BaseViewModel.kt (1)
postSideEffect(48-52)remote/oauth/src/main/java/com/teamsolply/solply/oauth/service/OauthService.kt (2)
postSocialLogin(10-16)postSocialLogin(11-15)data/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepositoryImpl.kt (1)
postSocialLogin(13-27)domain/oauth/src/main/java/com/teamsolply/solply/oauth/repository/OauthRepository.kt (2)
postSocialLogin(5-8)postSocialLogin(6-6)
🪛 detekt (1.23.8)
feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
[warning] 73-73: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (8)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (1)
129-130: 고정 높이로 변경된 Spacer의 반응형 동작을 확인하세요.이 Spacer가 weight 기반에서 고정 높이(16.dp)로 변경되었습니다. 고정 높이는 다양한 화면 크기에서 유연하게 대응하지 못할 수 있습니다. 특히 작은 화면에서는 레이아웃이 잘리거나 의도하지 않은 스크롤이 발생할 수 있습니다.
다양한 화면 크기(특히 작은 기기)에서 UI가 올바르게 표시되는지 확인해주세요.
feature/oauth/build.gradle.kts (1)
1-17: LGTM! Google Sign-In 의존성이 올바르게 추가되었습니다.Google Sign-In 통합에 필요한 Credential Manager API 라이브러리들(
google.id,credentials,credentials.play.auth)과 BuildConfig 접근을 위한 의존성이 적절하게 추가되었습니다.feature/oauth/src/main/res/values/strings.xml (1)
5-5: PR 목표와의 불일치 확인이 필요합니다.이 PR의 목표는 구글 로그인 구현인데,
apple_login문자열 리소스가 추가되었습니다. Apple 로그인이 이번 PR의 범위에 포함되는 것이 맞는지 확인이 필요합니다.feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt (2)
15-17: LGTM! Google 로그인 Intent가 일관성 있게 추가되었습니다.기존 Kakao 로그인 패턴을 따라 Google 로그인 Intent들이 적절하게 추가되었습니다. Click -> Success/Failure 흐름이 명확합니다.
27-27: LGTM! Google 로그인 SideEffect가 추가되었습니다.Kakao 로그인과 동일한 패턴으로
StartGoogleLoginSideEffect가 추가되어 일관성이 유지됩니다.core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt (1)
6-6: LGTM! BuildConfig 필드가 올바르게 추가되었습니다.
googleWebClientId필드가 data class에 적절하게 추가되었습니다.build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt (1)
37-41: 필드 이름의 오타를 수정하세요.BuildConfig 필드 이름에 오타가 있습니다:
GOOGLe_WEB_CLIENT_ID의e가 소문자입니다.GOOGLE_WEB_CLIENT_ID로 수정해야 합니다.다음 diff를 적용하세요:
buildConfigField( "String", - "GOOGLE_WEB_CLIENT_ID", + "GOOGLE_WEB_CLIENT_ID", gradleLocalProperties(rootDir, providers).getProperty("google.web.client.id") )Likely an incorrect or invalid review comment.
core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt (1)
4-4: LGTM! Google Web Client ID가 올바르게 구성되었습니다.BuildConfig에서
GOOGLE_WEB_CLIENT_ID를 가져와BuildConfigFields에 정상적으로 전달하고 있습니다.Also applies to: 18-18
| -keep class * extends com.google.gson.TypeAdapter | ||
| -keep class com.google.googlesignin.** { *; } No newline at end of file |
There was a problem hiding this comment.
🧩 Analysis chain
ProGuard 규칙이 지나치게 광범위할 수 있습니다.
현재 규칙이 매우 넓은 범위를 유지하고 있습니다:
* extends TypeAdapter: 모든 TypeAdapter 서브클래스 유지com.google.googlesignin.** { *; }: 모든 Google Sign-In 클래스와 멤버 유지
이는 난독화를 방해하고 APK 크기를 증가시킬 수 있습니다. Google Sign-In Credential Manager API를 사용하는 경우, 더 구체적인 규칙으로 충분할 수 있습니다.
다음 스크립트로 실제 사용되는 Google Sign-In 클래스를 확인하세요:
🏁 Script executed:
#!/bin/bash
# Google Sign-In 관련 import 및 사용 확인
rg -nP --type=kotlin -C3 'import.*google\.android\.gms\.auth|import.*googleid' feature/oauth/Length of output: 1223
🏁 Script executed:
#!/bin/bash
# Search for actual usage of com.google.googlesignin package
rg -nP --type=kotlin --type=java 'com\.google\.googlesignin' .Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for Gson TypeAdapter usage
rg -nP --type=kotlin --type=java 'TypeAdapter' .Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if Credential Manager classes appear in ProGuard rules already
cat app/proguard-rules.proLength of output: 847
🏁 Script executed:
#!/bin/bash
# Check build.gradle for dependencies
fd -e gradle | head -20Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search build files for google-signin or gson dependencies
rg -nP 'google.*signin|gson' build.gradleLength of output: 52
🏁 Script executed:
#!/bin/bash
# Search for any Credential Manager ProGuard rules or missing rules
rg -nP 'credentials|credential' app/proguard-rules.proLength of output: 0
사용되지 않는 ProGuard 규칙을 제거하세요.
검증 결과, 코드베이스 어디에서도 다음이 사용되지 않습니다:
com.google.googlesignin패키지 (검색 결과: 0건)TypeAdapter클래스 (검색 결과: 0건)
현재 구현은 com.google.android.libraries.identity.googleid (Credential Manager API)를 사용하고 있습니다. 라인 23-24의 규칙은 더 이상 필요하지 않은 레거시 Google Sign-In SDK 관련 규칙입니다.
이 규칙들을 제거하면 APK 크기를 줄이고 적절한 난독화가 가능합니다:
-keep class * extends com.google.gson.TypeAdapter
-keep class com.google.googlesignin.** { *; }
🤖 Prompt for AI Agents
In app/proguard-rules.pro around lines 23 to 24, remove the two unused legacy
ProGuard rules for Google Sign-In and Gson TypeAdapter (the lines keeping
com.google.googlesignin.** and classes extending com.google.gson.TypeAdapter);
delete those two lines, run a clean build to confirm no missing symbol or
runtime obfuscation issues, and if build/tests pass, commit the updated proguard
file.
| private val nonce = UUID.randomUUID().toString() | ||
| private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() | ||
| .setServerClientId(WEB_CLIENT_ID) // 웹 클라이언트 ID | ||
| .setFilterByAuthorizedAccounts(false) | ||
| // .setNonce(nonce) | ||
| .build() |
There was a problem hiding this comment.
🧩 Analysis chain
Nonce 사용이 권장됩니다.
Line 33에서 setNonce(nonce)가 주석 처리되어 있습니다. Nonce는 재생 공격(replay attack)을 방지하는 중요한 보안 메커니즘입니다. 특별한 이유가 없다면 활성화하는 것이 좋습니다.
Nonce를 활성화하세요:
private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID) // 웹 클라이언트 ID
.setFilterByAuthorizedAccounts(false)
-// .setNonce(nonce)
+ .setNonce(nonce)
.build()서버 측에서도 이 nonce를 검증하도록 구현해야 합니다.
이 리뷰 코멘트를 검증하기 위해 저장소를 분석하고 있습니다. 스크립트를 실행 중입니다.
🏁 Script executed:
#!/bin/bash
# Search for the GoogleLoginHelper file and examine its content
find . -name "GoogleLoginHelper.kt" -type f 2>/dev/null | head -5Length of output: 78
🏁 Script executed:
#!/bin/bash
# Search for GetGoogleIdOption imports and usage patterns
rg -n "GetGoogleIdOption" --type kotlin -B3 -A3Length of output: 1907
🏁 Script executed:
#!/bin/bash
# Search for nonce-related code and comments in the OAuth module
rg -n "nonce" --type kotlin -i -B2 -A2Length of output: 1167
🏁 Script executed:
#!/bin/bash
# Search for server-side validation of nonce
rg -n "nonce" --type java --type kotlin -B2 -A2Length of output: 1167
🏁 Script executed:
#!/bin/bash
# Check for build.gradle or pom.xml to identify Google Sign-In library version
find . -name "build.gradle*" -o -name "pom.xml" | xargs grep -l "google\|gms" 2>/dev/null | head -5Length of output: 96
🏁 Script executed:
#!/bin/bash
# Search for any comments or documentation explaining why nonce was commented out
rg -n "replay\|setNonce" --type kotlin --type java -B5 -A5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for server-side token validation logic
rg -n "verify|validate" --type kotlin --type java -i | grep -i "token\|nonce\|jwt\|google" | head -10Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check Google Play Services dependency version
find . -name "build.gradle*" | xargs cat 2>/dev/null | grep -A2 -B2 "play.*services.*auth"Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for any authentication verification controller or service
fd -e "\.kt$" -e "\.java$" | xargs grep -l "GoogleIdToken\|idToken\|verify" | head -10Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the full GoogleLoginHelper.kt file
find . -name "GoogleLoginHelper.kt" -type f 2>/dev/null -exec cat {} \;Length of output: 3518
🏁 Script executed:
#!/bin/bash
# Search for server endpoint that might validate Google tokens
rg -n "google.*token\|id.*token" --type kotlin --type java -i | head -15Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if there's documentation or configuration about security settings
find . -name "*.md" -o -name "*.txt" | xargs grep -l "nonce\|security" 2>/dev/null | head -5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for any existing issue tracker references or TODOs related to nonce
rg -n "TODO|FIXME|XXX" --type kotlin | grep -i "nonce\|security\|replay"Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for Google Sign-In implementation details
rg -n "credentialResponse\|credential" --type kotlin -B3 -A3 | head -30Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check for any security-related tests or validation logic
fd -e "Test.kt" -e "Test.java" | xargs grep -l "nonce\|GoogleLogin" 2>/dev/nullLength of output: 0
🏁 Script executed:
#!/bin/bash
# Look for the server-side implementation that receives the token
rg -n "id_token\|idToken" --type kotlin --type java | head -15Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for GoogleIdTokenVerifier or similar verification
rg -n "Verifier\|Verify" --type java --type kotlin | grep -i googleLength of output: 0
🏁 Script executed:
#!/bin/bash
# Check application properties or configuration for security settings
find . -name "application*.yml" -o -name "application*.properties" | xargs cat 2>/dev/null | grep -i nonceLength of output: 0
🏁 Script executed:
#!/bin/bash
# Look at git history to see if nonce was commented intentionally
git log --oneline feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt 2>/dev/null | head -10Length of output: 104
🏁 Script executed:
#!/bin/bash
# Check for any existing nonce validation on the backend
rg -n "setNonce\|getNonce\|validateNonce" --type java --type kotlinLength of output: 0
🏁 Script executed:
#!/bin/bash
# Look for any comments or documentation files
find . -path "*oauth*" -name "*.md" -o -path "*oauth*" -name "README*" 2>/dev/nullLength of output: 0
🏁 Script executed:
#!/bin/bash
# Get broader context about the OAuth implementation
find . -path "*oauth*" -type f \( -name "*.kt" -o -name "*.java" \) | head -20Length of output: 1603
🏁 Script executed:
#!/bin/bash
# Check for ID token validation on server
rg -n "verify.*IdToken\|validateIdToken" --type java --type kotlinLength of output: 0
🏁 Script executed:
#!/bin/bash
# Search for Google API client library usage
rg -n "GoogleIdTokenVerifier\|TokenVerifier" --type javaLength of output: 0
🏁 Script executed:
#!/bin/bash
# Check if there's any exception handling related to nonce
rg -n "NonceException\|InvalidNonce" --type java --type kotlinLength of output: 0
🏁 Script executed:
#!/bin/bash
# Look for the complete token exchange process
rg -n "exchange\|credential.*token" --type kotlin -B2 -A2 | head -30Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for server endpoint path that handles token verification
rg -n "@PostMapping\|@GetMapping\|@PutMapping" --type kotlin | grep -i "token\|auth\|google\|login"Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if nonce is being used elsewhere in the codebase
find . -type f \( -name "*.kt" -o -name "*.java" \) -exec grep -l "nonce" {} \;Length of output: 78
🏁 Script executed:
#!/bin/bash
# Look for documentation on Google Sign-In requirements
find . -name "*.md" -o -name "*.txt" -o -name "*.adoc" 2>/dev/null | xargs grep -l "Google\|OAuth\|Sign-In" 2>/dev/null | head -5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check for any vulnerability scanning or security audit files
find . -name "*.json" -o -name "*.yaml" | xargs grep -l "security\|vulnerability\|nonce" 2>/dev/null | head -5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Get imports from GoogleLoginHelper to understand the library version
rg -n "^import" feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt 2>/dev/null || rg -n "^import" . --type kotlin | grep -i google | head -10Length of output: 709
🏁 Script executed:
#!/bin/bash
# Check the Google Sign-In Android library documentation reference
cat build.gradle.kts 2>/dev/null || cat build.gradle 2>/dev/null || find . -name "build.gradle*" -type f | head -1 | xargs catLength of output: 637
🏁 Script executed:
#!/bin/bash
# Look for any recent commit messages about nonce
git log --all --grep="nonce" --oneline 2>/dev/null || echo "No git history available"Length of output: 0
🏁 Script executed:
#!/bin/bash
# Final check: get the exact structure of the GoogleLoginHelper.kt file around the nonce area
find . -name "GoogleLoginHelper.kt" -type f 2>/dev/null -exec head -50 {} \;Length of output: 1954
🏁 Script executed:
#!/bin/bash
# Look for server-side Google token verification configuration
find . -name "*.properties" -o -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep -i "google\|client.*id\|secret" 2>/dev/null | head -15Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check for Spring Security or other security framework configuration
rg -n "@EnableWebSecurity\|@Configuration\|securityFilterChain" --type java -B2 -A10 | head -40Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for JWT or token parsing logic
rg -n "JwtDecoder\|verifyIdToken\|parseToken" --type java -B2 -A5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for Google client library initialization
rg -n "GoogleCredential\|GoogleAuthorizationCodeFlow\|GoogleClientSecrets" --type javaLength of output: 0
🏁 Script executed:
#!/bin/bash
# Check if nonce validation happens in a filter or interceptor
rg -n "OncePerRequestFilter\|HandlerInterceptor" --type java -B2 -A10Length of output: 0
🏁 Script executed:
#!/bin/bash
# Get comprehensive view of OAuth module structure
find feature/oauth -type f 2>/dev/null | head -30Length of output: 525
🏁 Script executed:
#!/bin/bash
# Look at documentation or SECURITY.md files
find . -iname "*security*" -type f 2>/dev/null | head -10Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check for any architecture decision records or ADRs
find . -iname "*adr*" -o -iname "*decision*" -type f 2>/dev/null | head -10Length of output: 94
🏁 Script executed:
#!/bin/bash
# Final comprehensive search for nonce implementation
rg -rn "nonce" --type-list 2>/dev/null | head -5
rg -rn "nonce" . 2>/dev/null | head -20Length of output: 229
🏁 Script executed:
#!/bin/bash
# Check if setNonce is available in the GetGoogleIdOption API
rg -n "GetGoogleIdOption" . --type kotlin -A20 2>/dev/null | head -40Length of output: 4825
🏁 Script executed:
#!/bin/bash
# Look for version constraints or documentation
cat README.md 2>/dev/null | grep -i "google\|oauth\|security" || echo "No README or no matches"Length of output: 71
🏁 Script executed:
#!/bin/bash
# Check all files in the OAuth feature module
ls -la feature/oauth/ 2>/dev/null || find . -path "*oauth*" -type d | head -5Length of output: 289
🏁 Script executed:
#!/bin/bash
# Last attempt - find the exact file and show context
find . -name "GoogleLoginHelper.kt" 2>/dev/null -exec echo "=== File: {} ===" \; -exec cat {} \;Length of output: 3611
🏁 Script executed:
#!/bin/bash
# Check for Google Play Services version in dependencies
rg -n "com\.google\.android\.gms" . --type kotlin --type java | head -10Length of output: 306
🏁 Script executed:
#!/bin/bash
# Look for version strings
rg -n "gms:play-services.*auth" . | head -5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if there's a docs folder
find . -type d -name "docs" -o -type d -name "documentation" 2>/dev/nullLength of output: 0
🏁 Script executed:
#!/bin/bash
# Search for any CHANGELOG or release notes
find . -iname "CHANGELOG*" -o -iname "RELEASE*" 2>/dev/null | xargs grep -i nonce 2>/dev/nullLength of output: 0
🏁 Script executed:
#!/bin/bash
# Verify the exact lines mentioned in the review
sed -n '29,34p' feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt 2>/dev/null || echo "File path may differ"Length of output: 278
🌐 Web query:
Google Identity Services GetGoogleIdOption setNonce method API documentation
💡 Result:
setNonce is a method on GetGoogleIdOption.Builder that sets the nonce used when generating a Google ID token. Use it when you build GetGoogleIdOption to have the ID token include the provided nonce.
Example (Kotlin):
val googleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID)
.setNonce(nonce)
.build()
Docs: GetGoogleIdOption.Builder (method: setNonce). [1][2]
Sources
[1] GetGoogleIdOption.Builder — Google Developers reference.
[2] Authenticate users with Sign in with Google (Android) — example showing setNonce.
🌐 Web query:
androidx.credentials GetGoogleIdOption.Builder setNonce API
💡 Result:
Yes — GetGoogleIdOption.Builder has a setNonce(...) method. It accepts a String nonce and sets the nonce used when generating the Google ID token.
Signatures / usage examples:
- Kotlin: .setNonce(nonce: String?) on GetGoogleIdOption.Builder. [1]
- Java: public @nonnull GetGoogleIdOption.Builder setNonce(String nonce). [2]
Example (Kotlin):
val googleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID)
.setNonce(nonce)
.build()
Sources:
[1] Android Developers — Credential Manager: GetGoogleIdOption.Builder (example).
[2] Google Developers reference — GetGoogleIdOption.Builder API.
🌐 Web query:
Google Sign-In nonce replay attack prevention Android 2024 2025
💡 Result:
Short answer — use Google’s nonce support + server-side single-use verification and device attestation:
Key points and recommended implementation
- Include a cryptographically strong nonce in each Android Sign‑in with Google request (setNonce / setNonce(nonce)). This nonce is intended to prevent replay attacks. [1][2][3]
- Generate the nonce server‑side or in the app using a CSPRNG (unpredictable), encode URL‑safe Base64, and meet provider rules (recommend ≥16 bytes). Treat each nonce as single‑use. Store/hash it server‑side and mark consumed when an ID token/response is accepted. Reject reused or expired nonces. [2][4]
- Verify the ID token on your backend: verify signature, aud/client_id, exp/iat, and that the token contains the same nonce you issued. Only accept tokens whose nonce matches a currently valid, unused nonce. [1][3]
- Optionally harden high‑value flows by combining nonce with a request hash or a server‑issued unique value and using Play Integrity / SafetyNet attestation (nonce field) to bind the response to the specific request and detect tampering. Check and verify attestation tokens server‑side. [4][5]
- For OAuth code flows, also use PKCE on top of the above; for OIDC ID tokens rely on the nonce claim to prevent replay. [3]
Quick checklist
- Generate CSPRNG nonce (server or app), URL‑safe Base64, ≥16 bytes. [4]
- Call setNonce(...) when building the Google sign‑in request on Android. [1][2][6]
- Exchange/receive ID token on server; verify signature, aud, exp, iat, and nonce == stored value. Mark nonce used. [1][3]
- Consider Play Integrity / SafetyNet attestation and request-specific hashes for extra protection. [4][5]
Sources
[1] Google Identity Android docs — BeginSignInRequest / GetSignInIntentRequest nonce docs.
[2] Android Developer: Authenticate users with Sign in with Google (nonce guidance).
[3] Google Sign‑in / OpenID Connect guidance (ID token verification / nonce role).
[4] Play Integrity and SafetyNet docs — using nonce to prevent replay/tampering.
[5] Google Sign‑in Security bundle / claims (auth_time and additional trust signals).
Nonce 활성화 및 서버 측 검증 구현 필요
클라이언트: GetGoogleIdOption.Builder의 setNonce() 메서드가 Google 공식 API에서 지원되며, nonce는 재생 공격 방지를 위한 표준 보안 메커니즘입니다. 현재 nonce가 생성되었으나(29줄) 주석 처리되어 사용되지 않고 있습니다.
서버: 저장소 검색 결과 nonce 검증 로직이 존재하지 않습니다. Google 문서에 따르면 서버는 ID 토큰의 nonce 클레임을 발급된 nonce와 비교하여 검증해야 하며, 각 nonce는 단일 사용 용도로 취급되어야 합니다.
private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID) // 웹 클라이언트 ID
.setFilterByAuthorizedAccounts(false)
-// .setNonce(nonce)
+ .setNonce(nonce)
.build()서버 측에서 이 nonce를 ID 토큰의 nonce 클레임과 대조하여 검증하는 로직을 구현해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private val nonce = UUID.randomUUID().toString() | |
| private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() | |
| .setServerClientId(WEB_CLIENT_ID) // 웹 클라이언트 ID | |
| .setFilterByAuthorizedAccounts(false) | |
| // .setNonce(nonce) | |
| .build() | |
| private val nonce = UUID.randomUUID().toString() | |
| private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() | |
| .setServerClientId(WEB_CLIENT_ID) // 웹 클라이언트 ID | |
| .setFilterByAuthorizedAccounts(false) | |
| .setNonce(nonce) | |
| .build() |
🤖 Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around lines 29–34, the client creates a nonce but it's commented out and also
generated as a single class-level val; enable nonce usage and make it
per-login-request by generating a fresh UUID for each auth attempt and pass it
to GetGoogleIdOption.setNonce(nonce). On the server side, implement persistent
single-use nonce handling: when starting an auth flow persist the nonce (e.g.,
DB/Redis) with an expiry, accept the ID token from the client and decode its
nonce claim, compare it to the stored nonce, reject mismatches or
expired/nonexistent nonces, and delete/mark the nonce as used so it cannot be
reused.
| private val request: GetCredentialRequest = GetCredentialRequest.Builder() | ||
| .addCredentialOption(googleIdOption) | ||
| .build() | ||
| private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main) |
There was a problem hiding this comment.
생명주기를 고려하지 않은 CoroutineScope가 메모리 누수를 유발할 수 있습니다.
CoroutineScope(Dispatchers.Main)을 직접 생성하면 Activity/Fragment가 종료되어도 코루틴이 계속 실행될 수 있습니다.
다음 중 하나를 적용하세요:
- 권장: Activity의 lifecycleScope를 파라미터로 받기
class GoogleLoginHelper(
private val context: Context,
private val scope: CoroutineScope
)
// Usage: GoogleLoginHelper(context, lifecycleScope)- 대안: SupervisorJob을 사용하고 명시적으로 취소
private val job = SupervisorJob()
private val coroutineScope = CoroutineScope(Dispatchers.Main + job)
fun dispose() {
job.cancel()
}🤖 Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around line 38, the helper creates a raw CoroutineScope with Dispatchers.Main
which can outlive Activity/Fragment and cause memory leaks; change the class to
accept a CoroutineScope (preferably lifecycleScope) as a constructor parameter
and use that scope for launches, or alternatively create the scope with a
SupervisorJob combined with Dispatchers.Main and expose a dispose/clear method
that cancels the job; update usages to pass lifecycleScope from
Activities/Fragments when instantiating the helper.
| } catch (e: GetCredentialException) { | ||
| Log.e("Google Sign-in failed", " ${e.localizedMessage}") | ||
| } | ||
| } |
There was a problem hiding this comment.
에러 발생 시 onFailure 콜백이 호출되지 않습니다.
GetCredentialException이 발생하면 로그만 남기고 onFailure 콜백을 호출하지 않아 UI가 로그인 실패를 감지하지 못합니다. 사용자는 로그인이 진행 중인지 실패했는지 알 수 없게 됩니다.
다음과 같이 수정하세요:
} catch (e: GetCredentialException) {
Log.e("Google Sign-in failed", " ${e.localizedMessage}")
+ onFailure(e.localizedMessage ?: "구글 로그인에 실패하였습니다.")
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (e: GetCredentialException) { | |
| Log.e("Google Sign-in failed", " ${e.localizedMessage}") | |
| } | |
| } | |
| } catch (e: GetCredentialException) { | |
| Log.e("Google Sign-in failed", " ${e.localizedMessage}") | |
| onFailure(e.localizedMessage ?: "구글 로그인에 실패하였습니다.") | |
| } | |
| } |
🤖 Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around lines 51–54, the catch block for GetCredentialException currently only
logs the error so the UI never learns the login failed; update the catch to
invoke the provided onFailure callback with the exception (e.g., onFailure(e))
so failure is propagated to the caller, and if onFailure must run on the UI
thread, post/invoke it on the main thread before returning.
| } catch (e: GoogleIdTokenParsingException) { | ||
| // Log.e(TAG, "Received an invalid google id token response", e) | ||
| } |
There was a problem hiding this comment.
예외가 삼켜지고 있습니다.
GoogleIdTokenParsingException이 발생해도 아무 처리도 하지 않아 사용자가 실패를 알 수 없습니다. Detekt도 이를 경고하고 있습니다.
예외 발생 시 실패 콜백을 호출하세요:
} catch (e: GoogleIdTokenParsingException) {
-// Log.e(TAG, "Received an invalid google id token response", e)
+ Log.e(TAG, "Received an invalid google id token response", e)
+ onFailure("구글 로그인 토큰 파싱에 실패하였습니다.")
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (e: GoogleIdTokenParsingException) { | |
| // Log.e(TAG, "Received an invalid google id token response", e) | |
| } | |
| } catch (e: GoogleIdTokenParsingException) { | |
| Log.e(TAG, "Received an invalid google id token response", e) | |
| onFailure("구글 로그인 토큰 파싱에 실패하였습니다.") | |
| } |
🧰 Tools
🪛 detekt (1.23.8)
[warning] 73-73: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🤖 Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around lines 73-75, the catch block for GoogleIdTokenParsingException currently
swallows the exception; replace it so the exception is logged and the login
failure path is invoked (e.g., call the existing failure callback or listener
with the exception or an appropriate error object). Ensure you do not rethrow
unnecessarily, but propagate the error to the caller by invoking the module's
failure callback (and include the exception in the log message and callback
parameters).
| OauthSideEffect.StartGoogleLogin -> googleLoginHelper.requestGoogleLogin( | ||
| onSuccess = { accessToken -> | ||
| viewModel.sendIntent( | ||
| OauthIntent.GoogleLoginSuccess( | ||
| provider = "GOOGLE", | ||
| accessToken = accessToken | ||
| ) | ||
| ) | ||
| }, | ||
| onFailure = {} | ||
| ) | ||
|
|
There was a problem hiding this comment.
구글 로그인 로직이 중복되어 있습니다.
구글 로그인 로직이 두 곳에 중복 구현되어 있습니다:
- Lines 68-79:
OauthSideEffect.StartGoogleLogin사이드 이펙트 처리 - Lines 88-100:
googleLoginClick콜백에서 직접 호출
이는 의도하지 않은 동작을 유발할 수 있습니다. 카카오 로그인과 동일하게 ViewModel을 통한 단일 플로우만 사용하는 것을 권장합니다.
다음과 같이 수정하여 카카오 로그인과 일관된 패턴을 유지하세요:
OauthScreen(
kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) },
- googleLoginClick = {
- googleLoginHelper.requestGoogleLogin(
- onSuccess = { accessToken ->
- viewModel.sendIntent(
- OauthIntent.GoogleLoginSuccess(
- provider = "GOOGLE",
- accessToken = accessToken
- )
- )
- },
- onFailure = {}
- )
- }
+ googleLoginClick = { viewModel.sendIntent(OauthIntent.GoogleLoginClick) }
)Also applies to: 88-100
🤖 Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt around
lines 68-79 and 88-100 there is duplicated Google login logic: the same
googleLoginHelper.requestGoogleLogin call is invoked both from the
OauthSideEffect.StartGoogleLogin handler and from the googleLoginClick callback.
Remove the direct request call from the googleLoginClick callback (lines
~88-100) and instead dispatch a single ViewModel intent from the click handler
(e.g., send an intent like OauthIntent.StartGoogleLogin or similar) so that the
side-effect handler (lines ~68-79) is the sole place that calls
googleLoginHelper.requestGoogleLogin; also propagate success/failure through the
existing ViewModel intents rather than handling login directly in the UI
callback.
| ) | ||
| ) | ||
| }, | ||
| onFailure = {} |
There was a problem hiding this comment.
에러 처리가 누락되었습니다.
onFailure 콜백이 빈 람다 {}로 처리되어 있어 사용자가 로그인 실패를 알 수 없습니다. 최소한 로그를 남기거나 사용자에게 토스트/스낵바로 알려주는 것을 권장합니다.
예시:
onFailure = { errorMessage ->
Log.e("OauthScreen", "Google login failed: $errorMessage")
// TODO: Show error message to user
}Also applies to: 98-98
🤖 Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt around
lines 77 and 98, the onFailure callbacks are empty lambdas so login failures are
swallowed; update both callbacks to accept the error (or message) parameter, log
the error (e.g., Log.e with a clear tag and message) and surface feedback to the
user (show a Toast or Snackbar with a user-friendly message); ensure you handle
nullable/unknown error text safely and avoid crashing when showing the UI
feedback.
| OauthIntent.GoogleLoginClick -> postSideEffect(OauthSideEffect.StartGoogleLogin) | ||
|
|
||
| is OauthIntent.GoogleLoginSuccess -> postSocialLogin( | ||
| provider = intent.provider, | ||
| oauthAccessToken = intent.accessToken | ||
| ) | ||
|
|
||
| is OauthIntent.GoogleLoginFailure -> { | ||
| TODO() | ||
| } |
There was a problem hiding this comment.
Google 로그인 실패 처리를 구현해야 합니다.
Google 로그인 흐름이 추가되었으나 GoogleLoginFailure의 에러 처리가 TODO()로 남아있습니다. 이는 런타임 시 NotImplementedError를 발생시킵니다.
사용자에게 적절한 에러 메시지를 표시하거나 로그를 남기는 등의 에러 처리가 필요합니다.
Kakao 로그인 실패 처리도 동일하게 TODO()로 남아있습니다(Line 24-26). 두 경우 모두 에러 처리 구현이 필요합니다. 에러 처리 구현을 도와드릴까요?
🤖 Prompt for AI Agents
feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt around
lines 24-26 and 28-37: both KakaoLoginFailure and GoogleLoginFailure currently
call TODO() which throws NotImplementedError at runtime; replace each TODO with
concrete failure handling by posting an appropriate side effect (e.g.,
postSideEffect(OauthSideEffect.ShowError(...)) with a user-friendly message and
include/log the underlying error for diagnostics), and ensure any cleanup or
state reset needed after a failed social login is performed (e.g., clear loading
state or reset flags).
| credentials = "1.6.0-beta03" | ||
| googleid = "1.1.1" |
There was a problem hiding this comment.
🧩 Analysis chain
라이브러리 버전 확인이 필요합니다.
credentials 라이브러리가 베타 버전(1.6.0-beta03)을 사용하고 있습니다. 프로덕션 환경에서는 안정 버전 사용을 권장합니다.
다음 명령으로 최신 안정 버전을 확인하세요:
🌐 Web query:
What is the latest stable version of androidx.credentials library?
💡 Result:
The latest stable release is androidx.credentials:credentials version 1.5.0 (released March 12, 2025). [1]
Sources
[1] Android Jetpack — Credentials release notes.
최신 정보를 확인하기 위해 추가 검색이 필요합니다.
프로덕션 환경에서는 안정 버전(1.5.0)을 사용하는 것을 권장합니다.
코드가 아직 베타 단계인 androidx.credentials:credentials:1.6.0-beta03을 사용 중입니다. 최신 안정 버전은 1.5.0입니다. 베타 버전은 미완성 기능과 잠재적 버그가 포함될 수 있으므로, 프로덕션 배포 전에 안정 버전으로 변경하세요.
credentials = "1.5.0"으로 변경하거나, 1.6.0 베타 버전 사용이 필수인 경우 그 이유를 명시하세요.
🤖 Prompt for AI Agents
gradle/libs.versions.toml lines 93-94: the project references the beta artifact
androidx.credentials:credentials 1.6.0-beta03 which is not recommended for
production; update the version to the latest stable release by changing
credentials = "1.6.0-beta03" to credentials = "1.5.0", or if you must keep the
beta, add a short comment next to the entry explaining why the beta is required
and what feature or fix depends on it.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (1)
129-131: 고정 높이와 weight 혼용으로 인한 레이아웃 불일치일부 Spacer만 고정 높이(
height)로 변경되었고, Line 197의Spacer(modifier = Modifier.weight(12f))는 그대로 유지되어 있습니다. 이로 인해:
- 고정 높이 Spacer들은 화면 크기에 관계없이 항상 동일한 공간을 차지
- weight Spacer는 남은 공간을 모두 차지
Arrangement.SpaceBetween과 조합 시 의도치 않은 레이아웃 발생 가능화면 크기별 대응이 필요하다면 weight 방식으로 통일하거나, 고정 레이아웃이 의도라면 모든 Spacer를 고정 높이로 변경하는 것을 권장합니다.
♻️ 일관된 방식으로 수정 제안 (고정 높이 방식)
- Spacer(modifier = Modifier.weight(12f)) + Spacer(modifier = Modifier.weight(1f))또는 모든 spacer를 weight 기반으로 복원:
- modifier = Modifier.height(16.dp) + modifier = Modifier.weight(1f)- Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.weight(2f))- Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.weight(1.5f))Also applies to: 141-141, 169-169
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.ktfeature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt
🔇 Additional comments (1)
feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt (1)
117-124:Arrangement.SpaceBetween과 Spacer 조합 재검토 필요현재 Column이
verticalArrangement = Arrangement.SpaceBetween을 사용하고 있어 자식 요소들 사이에 자동으로 균등한 간격이 배분됩니다. 이 상태에서 고정 높이 Spacer를 추가하면 Spacer 자체가 "콘텐츠"로 간주되어 의도한 레이아웃과 다르게 동작할 수 있습니다.
SpaceBetween을 유지하면서 특정 간격을 조절하려면 Spacer 대신 해당 컴포넌트의padding이나Modifier.padding()을 사용하는 것이 더 명확합니다.
📌 PR 요약
🌱 작업한 내용
🌱 PR 포인트
📸 스크린샷
📮 관련 이슈
Summary by CodeRabbit
새로운 기능
개선
작업
✏️ Tip: You can customize this high-level summary in your review settings.